/*
  Copyright (c) 2014 Dimitrij Gester

  "Smart RC TX" is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  "Smart RC TX" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with "Smart RC TX"; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

package com.codeplex.smartrctx.configurator.main;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.simpleframework.xml.core.Persister;

import rcremote.main.R;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.ProgressBar;
import android.widget.Toast;
import android.widget.ViewFlipper;

import com.codeplex.smartrctx.configurator.communication.BluetoothCommLink;
import com.codeplex.smartrctx.configurator.communication.CommLink;
import com.codeplex.smartrctx.configurator.communication.Transceiver;
import com.codeplex.smartrctx.configurator.main.helpers.FlipGestureDetector;
import com.codeplex.smartrctx.configurator.main.model.RCSettings;
import com.codeplex.smartrctx.configurator.main.model.control_commands.ModelSerializer;
import com.codeplex.smartrctx.configurator.main.model.control_commands.Serializer;

public class PaginatorActivity extends Activity implements TabListener {

	private class EmuDetector {
		private boolean isEmu;

		public EmuDetector() {
			isEmu = Build.HARDWARE.equals("goldfish");
		}

		public boolean isEmulator() {
			return isEmu;
		}
	}

	private static final int REQUEST_SETTINGS = 0;
	private static final int REQUEST_BT_ENABLE = 1;
	private ViewFlipper flipper;
	private GestureDetector gestureDetector;
	private SharedPreferences prefs;
	private CommLink comm;
	private Transceiver transmitter;
	private ProgressBar progressBar;
	private EmuDetector emu;
	private boolean connectedManually;

	long txDelay = 100;
	private TransmissionHelper th;

	@SuppressLint("DefaultLocale")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.multi_channel_editor);

		prefs = PreferenceManager.getDefaultSharedPreferences(this);

		emu = new EmuDetector();

		txDelay = 1;
		String s = this.prefs.getString("txDelayPreference", "100");
		try {
			txDelay = Long.parseLong(s);
		} catch (NumberFormatException e) {
		}

		ActionBar ab = getActionBar();
		ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

		flipper = (ViewFlipper) findViewById(R.id.multiChannelEditor);
		gestureDetector = new GestureDetector(this, new FlipGestureDetector(
				this, flipper));

		ab.addTab(ab.newTab().setText("log").setTabListener(this));

		String titles[] = getResources().getStringArray(R.array.channels);
		for (int i = 0; i < RCSettings.CHANNEL_COUNT; ++i) {
			ab.addTab(ab.newTab().setText(titles[i]).setTabListener(this));
		}

		OneChannelEditor e;

		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder1);
		e.setCurrentChannel(0);
		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder2);
		e.setCurrentChannel(1);
		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder3);
		e.setCurrentChannel(2);
		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder4);
		e.setCurrentChannel(3);
		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder5);
		e.setCurrentChannel(4);
		e = (OneChannelEditor) getFragmentManager().findFragmentById(
				R.id.fragmentPlaceholder6);
		e.setCurrentChannel(5);

		logView = (LogViewFragment) getFragmentManager().findFragmentById(
				R.id.fragmentLogView);

		progressBar = (ProgressBar) findViewById(R.id.progressBar1);

		try {
			readTxDelayPreference();
			if (emu.isEmulator()) {
				this.comm = null;
			} else {
				this.comm = new BluetoothCommLink();
			}
			this.transmitter = new Transceiver(this.comm, this.txDelay);

			this.comm.addOnReceiveHandler(logView);
		} catch (Exception ex) {
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.configurator, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {

		case R.id.action_serialize:
			logView.clearLog();
			Serializer s = new ModelSerializer();
			List<List<Byte>> b = s.serialize();

			List<Byte> rstCmd = new ArrayList<Byte>();
			rstCmd.add((byte) 0xFF);
			b.add(0, rstCmd);

			transmit(b);
			break;
		case R.id.action_clear:
			clear();
			break;
		case R.id.action_save:
			save();
			break;
		case R.id.action_load:
			load();
			break;
		case R.id.action_fix:
			List<List<Byte>> cmds2 = new ArrayList<List<Byte>>();
			List<Byte> fixCmd = new ArrayList<Byte>();
			fixCmd.add((byte) 0xC1);
			cmds2.add(fixCmd);
			transmit(cmds2, true);

			break;
		case R.id.action_dump_config:
			List<List<Byte>> cmds3 = new ArrayList<List<Byte>>();
			List<Byte> dmpCmd = new ArrayList<Byte>();
			dmpCmd.add((byte) 0xC0);
			cmds3.add(dmpCmd);
			transmit(cmds3, true);

			break;
		case R.id.action_connect:
			connect();
			break;
		case R.id.action_disconnect:
			disconnect();
			break;
		case R.id.action_settings:
			openPreferences();
			break;
		default:
			return false;
		}
		return true;
	}

	private class StorageShelf {
		boolean mExternalStorageAvailable = false;
		boolean mExternalStorageWriteable = false;

		public StorageShelf() {
			String state = Environment.getExternalStorageState();

			if (Environment.MEDIA_MOUNTED.equals(state)) {
				// We can read and write the media
				mExternalStorageAvailable = mExternalStorageWriteable = true;
			} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
				// We can only read the media
				mExternalStorageAvailable = true;
				mExternalStorageWriteable = false;
			} else {
				// Something else is wrong. It may be one of many other states,
				// but all we need
				// to know is we can neither read nor write
				mExternalStorageAvailable = mExternalStorageWriteable = false;
			}
		}

		public OutputStream getOutputFileStream() {
			OutputStream stream = null;

			if (mExternalStorageAvailable && mExternalStorageWriteable) {
				// File dir = getExternalFilesDir(null);
				File pubDir = Environment
						.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
				File target = new File(pubDir, "MixerSettings1.xml");

				try {
					pubDir.mkdirs();
					target.createNewFile();
					FileOutputStream fs = new FileOutputStream(target);
					stream = new BufferedOutputStream(fs);
				} catch (IOException e) {
					Log.w("External Storage", e);
				}
			}
			return stream;
		}

		public InputStream getInputFileStream() {
			InputStream stream = null;

			if (mExternalStorageAvailable && mExternalStorageWriteable) {
				// File dir = getExternalFilesDir(null);
				File pubDir = Environment
						.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
				File target = new File(pubDir, "MixerSettings1.xml");

				try {
					FileInputStream fs = new FileInputStream(target);
					stream = new BufferedInputStream(fs);
				} catch (IOException e) {
					Log.w("External Storage", e);
				}
			}
			return stream;
		}
	}

	private void clear() {
		RCSettings.newSettings();
	}

	private void save() {
		StorageShelf store = new StorageShelf();
		org.simpleframework.xml.Serializer s = new Persister();
		OutputStream stream = store.getOutputFileStream();
		if (stream != null) {
			try {
				s.write(RCSettings.getSettings(), stream);
			} catch (Exception e) {
				Log.w("Save", e);
			} finally {
				try {
					stream.close();
				} catch (Exception e) {
				}
			}
		}
	}

	private void load() {
		StorageShelf store = new StorageShelf();
		org.simpleframework.xml.Serializer s = new Persister();
		InputStream stream = store.getInputFileStream();
		if (stream != null) {
			try {
				RCSettings settings = s.read(RCSettings.class, stream);
				RCSettings.initSettings(settings);
			} catch (Exception e) {
				Log.w("Load", e);
			} finally {
				try {
					stream.close();
				} catch (Exception e) {
				}
			}
		}
	}

	void openPreferences() {
		Intent i = new Intent(this, SettingsActivity.class);
		startActivityForResult(i, REQUEST_SETTINGS);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		switch (requestCode) {
		case REQUEST_SETTINGS:
			if (resultCode == Activity.RESULT_OK)
				readTxDelayPreference();

			break;
		case REQUEST_BT_ENABLE:
			if (resultCode == RESULT_OK) {
				// connect();
			}
			break;
		}
	}

	private void readTxDelayPreference() {
		txDelay = 100;
		String s = this.prefs.getString("txDelayPreference", "100");
		try {
			txDelay = Long.parseLong(s);
		} catch (NumberFormatException e) {
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return !this.gestureDetector.onTouchEvent(event);
	}

	private void transmit(List<List<Byte>> data) {
		transmit(data, true);
	}

	private class Disconnector extends TimerTask {
		@Override
		public void run() {
			try {
				synchronized (comm) {
					comm.disconnect();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private Timer disconnectTimer;
	private LogViewFragment logView;

	private class TransmissionHelper extends AsyncTask<Void, Integer, Boolean>
			implements Transceiver.ProgressReceiver {
		private List<List<Byte>> data;
		private boolean confirm;

		public TransmissionHelper(List<List<Byte>> data, boolean confirm) {
			this.data = data;
			this.confirm = confirm;
		}

		@Override
		protected void onPreExecute() {
			progressBar.setMax(100);
			progressBar.setProgress(0);
		}

		@Override
		protected Boolean doInBackground(Void... params) {
			try {
				synchronized (comm) {
					if (disconnectTimer != null) {
						disconnectTimer.cancel();
						disconnectTimer = null;
					}

					comm.connect();
					transmitter.setProgressReceiver(this);
					transmitter.sendCommands(data, confirm);

					if (!connectedManually) {
						disconnectTimer = new Timer();
						disconnectTimer.schedule(new Disconnector(), 7000);
					}
				}
			} catch (IOException e1) {
				return false;
			}

			return true;
		}

		@Override
		protected void onPostExecute(Boolean result) {
			if (!result) {
				Toast.makeText(PaginatorActivity.this, R.string.confError,
						Toast.LENGTH_LONG).show();
				Log.w("BT Connection", "could not send configuration");
			}
		}

		@Override
		protected void onProgressUpdate(Integer... values) {

			progressBar.setProgress(values[0]);
		}

		@Override
		public void setProgress(float progress) {
			publishProgress((int) (progress * 100));
		}
	}

	private class ConnectHelper extends AsyncTask<Void, Void, Void> {
		private boolean connect;

		public ConnectHelper(boolean connect) {
			this.connect = connect;
		}

		@Override
		protected Void doInBackground(Void... params) {
			try {
				synchronized (comm) {
					if (disconnectTimer != null) {
						disconnectTimer.cancel();
						disconnectTimer = null;
					}

					if (this.connect) {
						comm.connect();
					} else {
						comm.disconnect();
					}
					connectedManually = this.connect;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

			return null;
		}

	}

	private void connect() {
		prepareTransmissionLink();
		ConnectHelper ch = new ConnectHelper(true);
		ch.execute();
	}

	private void disconnect() {
		ConnectHelper ch = new ConnectHelper(false);
		ch.execute();
	}

	private void transmit(List<List<Byte>> data, boolean withConfirmation) {
		prepareTransmissionLink();

		th = new TransmissionHelper(data, withConfirmation);
		th.execute();
	}

	private void prepareTransmissionLink() {
		if (!this.comm.isEnabled()) {
			this.requestActivateBluetooth();
			return;
		}

		if (this.comm.isConnected())
			return;

		String p = this.prefs.getString("selectedCommDevice", "NO DEVICE");
		if (emu.isEmulator()) {
			p = "192.168.2.149";
		}

		if (p.equals("NO DEVICE")) {
			Toast.makeText(this, R.string.selectBTconn, Toast.LENGTH_LONG)
					.show();
			this.openPreferences();
			return;
		}

		try {
			this.comm.setCommTarget(p);
		} catch (Exception ex) {
			Log.e("CONNECTION", "could not connect", ex);
		}
	}

	void requestActivateBluetooth() {
		Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
		this.startActivityForResult(btEnable, REQUEST_BT_ENABLE);
	}

	// ---------------------------------------------------------------
	// ---------------- tab switching --------------------------------
	// ---------------------------------------------------------------
	@Override
	public void onTabReselected(Tab tab, FragmentTransaction transaction) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onTabSelected(Tab tab, FragmentTransaction transaction) {
		this.flipper.setInAnimation(this, R.anim.in_right);
		this.flipper.setOutAnimation(this, R.anim.out_left);
		this.flipper.setDisplayedChild(tab.getPosition());
	}

	@Override
	public void onTabUnselected(Tab tab, FragmentTransaction transaction) {
		// TODO Auto-generated method stub

	}

	// ---------------------------------------------------------------
	// ---------------------------------------------------------------
	// ---------------------------------------------------------------
}
